﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading;

namespace BackgroundTask {
    /// <summary>
    /// Provides a generic typed Pool for running Tasks/Funcs in an extra thread 
    /// </summary>
    /// <typeparam name="TParam">The methods parameter type</typeparam>
    /// <typeparam name="TReturn">The methods return value type</typeparam>
    public class BackgroundActionPool<TParam, TReturn> {
        /// <summary>
        /// Will be raised when all actions in the list are "done"
        /// </summary>
        public event EventHandler OnAllDone;
        /// <summary>
        /// Will be raised when the next action in the list is called
        /// </summary>
        public event EventHandler OnNextProgress;
        /// <summary>
        /// Will be raised when Cancel() was called
        /// </summary>
        public event EventHandler RunCanceled;
        /// <summary>
        /// Indicates whether the Run is done
        /// </summary>
        public bool Done { get; set; }
        /// <summary>
        /// Gets or sets the current tasks name (status text)
        /// </summary>
        public String CurrentProgressName { get; set; }

        /// <summary>
        /// Indicates whether the run was canceled
        /// </summary>
        public bool Canceled { get; set; }

        /// <summary>
        /// Gets or sets the list of actions to do
        /// </summary>
        private readonly List<BackgroundAction<TParam, TReturn>> m_lstActions;

        /// <summary>
        /// Gets or sets the count of done actions
        /// </summary>
        private int m_iActionsDone = 0;

        /// <summary>
        /// Gets or sets the current running action index
        /// </summary>
        private int m_iCurrentIndex = 0;

        /// <summary>
        /// Gets the current progress percentage
        /// </summary>
        public int CurrentPercentage {
            get {
                double dDivider = (double)(m_iCurrentIndex +1) / (m_lstActions.Count);
                dDivider *= 100;
                return (int)dDivider;
            }
        }

        /// <summary>
        /// Returns a new BackgroundAction object containing the current generic types signature
        /// </summary>
        /// <param name="action">An action</param>
        /// <param name="param">The parameter to pass to the action</param>
        /// <returns></returns>
        public BackgroundAction<TParam, TReturn> Create (Func<TParam, TReturn> action, TParam param) {
            return Create(action, param, null);
        }

        /// <summary>
        /// Returns a new BackgroundAction object containing the current generic types signature
        /// </summary>
        /// <param name="action">An action</param>
        /// <param name="param">The parameter to pass to the action</param>
        /// <param name="statusText">The actions description</param>
        /// <returns></returns>
        public BackgroundAction<TParam, TReturn> Create (Func<TParam, TReturn> action, TParam param, String statusText) {
            BackgroundAction<TParam, TReturn> a = new BackgroundAction<TParam, TReturn>(action, param);
            return a;
        }

        /// <summary>
        /// Initializes a new BackgroundActionPool object
        /// </summary>
        public BackgroundActionPool () {
            m_lstActions = new List<BackgroundAction<TParam, TReturn>>();
            CurrentProgressName = "< Pool not running >";
            Done = true;
        }

        /// <summary>
        /// Adds a background action to the to do list
        /// </summary>
        /// <param name="a"></param>
        /// <param name="name"></param>
        public void Add (BackgroundAction<TParam, TReturn> a, String name) {
            a.OnDone += a_OnDone;
            a.StatusText = name;
            m_lstActions.Add(a);
            Done = false;
        }

        /// <summary>
        /// Adds a background action to the to do list 
        /// </summary>
        /// <param name="a"></param>
        public void Add (BackgroundAction<TParam, TReturn> a) {
            this.Add(a, 
                     (m_lstActions.Count + 1).ToString(CultureInfo.InvariantCulture));
        }
		
        /// <summary>
        /// Performs the current action in running loop and cancels then
        /// </summary>
        public void Cancel() {
            if (Done) {
                if (RunCanceled != null)
                    RunCanceled(this, null);
            }
            Canceled = true;
        }

        /// <summary>
        /// Runs the to do loop
        /// </summary>
        public void Run() {
            Next();
        }

        /// <summary>
        /// Triggers the next action in the to do list to perform
        /// </summary>
        private void Next() {
            if (Canceled) {
                if (RunCanceled != null)
                    RunCanceled(this, null);
                return;
            } // if end
            if (m_iCurrentIndex < m_lstActions.Count) {
                var action = m_lstActions[m_iCurrentIndex];
                CurrentProgressName = action.StatusText;
                if (OnNextProgress != null) {
                    OnNextProgress(this, EventArgs.Empty);
                } // if end
                // Task<Object> taskAction = new Task<Object>();

                action.Do();
                Thread.Sleep(100);
            } // if end
        }

        /// <summary>
        /// This method is called when an action is done. Then it increments the current index
        /// and runs the next, when available. If not, it will raise the OnAllDone-Event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void a_OnDone (object sender, EventArgs e) {
            m_iActionsDone++;
            m_iCurrentIndex++;
            if (m_iActionsDone == m_lstActions.Count) {
                if (OnAllDone != null) {
                    OnAllDone(this, EventArgs.Empty);
                } // if end
                Done = true;
            } // if end
            else {
                Next();
            }
        }
    }
}